org.springframework.beans
包遵循JavaBeans提供的标准。一个JavaBean是一个简单的类,有一个无参的构造函数,遵循一个命名约定(举例来说)名为bingoMadness
的属性将具有setter方法setBingoMadness(..)
和getter方法getBingoMadness()
。更多关于JavaBeans的规范,请查看Oracle的网站(javabeans)。
beans包里一个相当重要的类是BeanWrapper
接口和它的实现(BeanWrapperImpl
)。根据Java文档,BeanWraper
提供了(单独或者批量的)设置和获取属性值的功能,获取属性的描述,或者查询属性是否可读或者可写的。同时,BeanWrapper
提供了对嵌套的属性的支持,可以无限深度的设置属性的子属性。BeanWrapper
提供了添加JavaBeans的PropertyChangeListeners
和VetoableChangeListeners
的能力,而不需要在目标类中添加其他代码。最后,BeanWrapper
提供了设置索引属性的支持。BeanWrapper
通常不会在应用程序代码中直接被使用,而是通过DataBinder
和BeanFacotry
。
BeanWrapper
的工作方式就像它名字描述的一样:它对Bean进行包装,然后像Bean一样工作,比如设置和读取属性。
设置和读取属性可以通过setPropertyValues(s)
和getPropertyValue(s)
方法,每个方法都有一系列的重载的变体。Spring Java文档提供了更详细的描述。重要的是要知道几个用于指示对象属性的约定。几个例子:
表8.1 Example of properties
Expression | Explanation
---|---
name
| 指明name
属性根据getName()
或isName()
和setName()
account.name
| 指明account
嵌套的属性name
,像getAccount().setName()
或者getAccout().getName()
account[2]
|指明account
中的第三个属性。有序的属性可以通过array
,list
或是其他自然的有序集合表示。
account[COMPANYNAME]
|指明account
下的Map形式的属性,以COMPANYNAME作为键去搜索。
下面你会发现BeanWrapper
设置和获取属性的一些例子。
(如果你不打算直接使用BeanWrapper
,下面的章节对你来说并不十分重要。如果你仅仅是使用DataBinder
和BeanFactory
和其开放的实现,你可以跳过关于PropertyEditors
的章节。)
考虑以下两个例子:
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
下列代码展示了如何检索和操作Companies
和Employees
属性:
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
Spring通过使用PropertyEditors
的属性来实现Object
和String
之间的转换。如果你仔细想想,有时用其他方式表示属性而不是对象本事是很方便的。比如,Date
可以以一种可读的方式表现(作为String``2017-14-09
),然而我们仍需要从可读的形式转换为对象(甚至:需要将任意人类可读的形式转换成Date
)。这可以通过注册自定义的java.beans.PropertyEditor
来实现。为BeanWrapper
或是在前几章提到的特定的IoC容器注册自定义的编辑器,使它们知道如何将属性转成期待的类型。更多有关PropertryEditors
的信息请查看Oracle提供的关于java.beans
的文档。
以下是Spring中使用属性编辑的例子:
当bean创建完的时候通过PropertyEditors
来设置属性。当一个属性在XML文件中声明的值是以java.lang.String
的形式存在时,(如果它的set方法有Class属性)Spring将用ClassEditor
来尝试解析Class
。
在Spring MVC中,解析HTTP请求的参数是通过各种PropertyEditors
完成的,你可以在CommnadController
的所有子类中手动绑定PropertyEditor
。
Spring有一系列内置的PropertyEditors
让这一切变得简单。这些都列在下面,它们都在org.springframework.beans.propertyeditors
包中。大部分,并非全部(下列已经指明),都默认的被注册到BeanWrapperImpl
。当然你也可以注册你自己的属性编辑器让它以某种方式工作:
表8.2 Built-in PropertyEditors
类 | 说明 |
---|---|
ByteArrayPropertyEditor |
自己数组的编辑器。字符串可以很容易的被转成对应的字节。被BeanWrapperImpl 默认注册 |
ClassEditor |
将字符串解析成实际的类,反之亦然。没有找到对应的类时,会抛出IllegalArgumentException 。被BeanWrapperImpl 默认注册。 |
CustomBooleanEditor |
布尔值的可定制的属性编辑器。默认被BeanWrapperImpl 注册,但是,可以被自定义的编辑器实例覆盖。 |
CustomCollectionEditor |
容器类的属性编辑器,将任意表示Collecion 的类型转换成给定的Collection 类型。 |
CustomDateEditor |
可定制的java.util.Date 属性编辑器,支持自定义的日期格式。没有被默认注册,需要使用者根据需要注册合适的格式。 |
CustomNumberEditor |
任意Number 子类像Integer ,Long ,Float ,Double 的可定制的编辑器。默认被BeanWrapperImpl 注册,但是可以注册自己的实例来覆盖它。 |
FileEditor |
能够将字符串解析成java.io.File 对象。默认被BeanWrapperImpl 注册。 |
InputStreamEditor |
单向属性编辑器,能够接受一个文本字符串并生成(通过中间ResourceEditor 和Resource )一个InputStream ,所以InputStream 属性可以直接设置为字符串。注意默认的使用不会为你关闭InputStream !默认被BeanWrapperImpl 注册 |
LocaleEditor |
能够将字符串解析成Locale 对象,反之亦然(字符串的格式是[country][variant],这和Locale的toString()方法相同)。默认被BeanWrapperImpl 注册 |
PatternEditor |
能够将字符串解析成java.util.regex.Pattern 对象,反之亦然。 |
PrppertiesEditor |
能够将(根据java.util.Properties 类的文档定义的形式的)字符串解析成Properties 对象。默认被BeanWrapperImpl 的注册。 |
StringTrimmerEditor |
修剪字符串的编辑器,允许将空字符串转换成null 。未被默认注册,需要根据需要自行注册。 |
URLEditor |
可以将代表URL的字符串解析成真实的URL 对象。默认被BeanWrapperImpl 注册。 |
Spring通过使用PropertyEditorManager
来设置可能需要的属性编辑器的搜索路径。搜索的路径也包括了sun.bean.editors
,它包括了对Font
和Color
类型以及大部分基本类型的PropertyEditor
实现。需要注意的是,标准的JavaBeans基础结构将会自动的发现Property
类(如果它们与它处理的类位于同一个包中,并且与该类拥有相同的名称,只是附加了"Editor",则不需要显示注册);例如,可以有以下类和包结构,这足以使得FooEditor
类被标识并作为Foo
类型属性的PropertyEditor
。
com
└chank
└pop
├Foo
└FooEditor // the PropertyEditor for the Foo class
请注意,您也可以在这里使用标准的BeanInfo
JavaBeans机制(在这里描述的不是令人惊讶的细节)。 查找下面的示例,使用BeanInfo
机制显式注册一个或多个PropertyEditor
实例和关联类的属性。
com
└chank
└pop
├Foo
└FooEditor // the BeanInfo for the Foo class
这里是FooBeanInfo
类的源码。它讲CustomNumberEditor
和Foo
类的age
属性相关联。
public class FooBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
当bean属性设置为字符串时,Spring容器最终将会用标准的JavaBeans属性编辑器将字符串转成属性的复杂类型。Spring预注册了一系列定制的属性编辑器(例如,将一个字符串表示的类名转换成真是的Class对象)。另外,标准的JavaBeans属性编辑器查找机制允许一个被恰当命名且位于同一个包中的PropertyEditor
类被自动的找到。
如果有需要注册其他定制的PropertyEditors
,有几个办法是可行的。大部分手动的方法是,假设你有一个BeanFactory
的引用,就可以用ConfiguraleBeanFactory
接口的registerCustomEditor()
方法,这通常都不是便利的,因此也不推荐。另一个稍微渐变的机制是使用一个叫CustomEditorConfigurer
的特使的后置处理器。尽管Bean Factory的后置处理器可以和Bean Factory的实现一起使用,但是CustomEditorConfigurer
具有嵌套的属性设置,因此强烈建议将其与Application Context
一起使用,可以和其它bean以相同的方式发布,会被自动的检测和应用。
注意,所有的Bean Factory或是Application Context都会自动使用一系列内置的属性编辑器,通过使用BeanWrapper
来处理属性转换。BeanWrapper
注册的标注的属性编辑器已经在之前的小节里列出。另外,ApplicationContext还会额外的添加或覆盖一些编辑器以适应特定的应用程序上下文类的方式处理查找资源。
标准的JavaBeans的PropertyEditor
实例被用来将传递的字符串属性值转换成实际的复杂的属性类型。CustomEditorConfigurer
,一个Bean Factory的后置处理器,可以很方便的被用来给一个Application Context
添加PropertyEditor
实例。
考虑一个用户的类ExoticType
,和另一个需要ExoticType
作为属性的DependsOnExoticType
类:
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
当设置属性的时候,我们希望将属性当作字符串,然后,PropertyAEditor
会将其转换成实际的ExoticType
实例:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
PropertyEditor
的实现像这样:
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
最终,我们用CustomEditorConfigurer
去注册新的PropertyEditor
到ApplicationContext
,使得它像希望的那样工作:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
另一个注册属性编辑器到Spring容器的机制是创建和使用PropertyEditorRegistrar
。当你希望在不同的场景里注册一系列属性编辑器时,这个接口是很有效的:写一个对应的registrar,并在各种情况下重复使用。
PropertyEditorRegistars
和PropertyEditorRegistry
的接口一起工作。PropertyEditorRegistry
被Spring的BeanWrapper
(和DataBinder
)实现。PropertyEditorRegistrars
和CustomEditorConfigurer
(在这里介绍)结合使用时特别方便,它提供了一个名为setPropertyEditorRegistaras(...)
的方法:以这种方式添加到的CustomEditorConfigurer
的PropertyEditorRegistrars
可以很容易的被DataBinder
和Spring MVC的Controller
共享。另外,它也避免了同步自定义的编辑器:一个PropertyEditorRegistrar
为每个需要的Bean创建全新的属性编辑器实例。
最好的例子就是去使用PropertyEditorRegistrar
。首先,你需要创建一个你自己的PropertyEditorRegistrar
实例:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
也可以参考org.springfrawork.beans.support.ResourceEditorRegistrar
作为PropertyEditorRegistrar
实现的例子。注意在它registerCustomEditors(..)
的实现中是如何创建每个属性编辑器的实例的。
然后,我们配置CustomEditorConfigurer
,将CustomPropertyEditorRegistrar
注入:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后,可能有些超出本章的重点,对于那些使用Spring MVC框架的人来说,使用PropertyEditorRegistrars
来为Controller
(比如SimpleFormController
)绑定数据非常方便。下面的例子在initBinder(..)
方法中用到了PropertyEditorRegistrar
:
public final class RegisterUserController extends SimpleFormController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods to do with registering a User
}
这种注册PropertyEditor
的风格可以让代码简洁(initBinder(..)
实现只有一行!),并且允许将常用的PropertyEditor
注册代码封装到一个类里,被许多需要的Controllers
共享。